﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using InfiniTec;

namespace ApiScannerShell
{
    public abstract class ViewModel<TViewModel, TModel>: IViewModel<TModel>, INotifyPropertyChanged, IViewModel
        where TViewModel: IViewModel<TModel>
    {
        private readonly Dictionary<string, object> _Values = new Dictionary<string, object>();
        private readonly Dictionary<string, ModelMapping<TModel>> _Mappings = new Dictionary<string, ModelMapping<TModel>>();
        private readonly Dictionary<string, IViewModel> _ChildViewModels = new Dictionary<string, IViewModel>();
        private readonly Dictionary<string, Func<string>> _ValidationRules = new Dictionary<string, Func<string>>();

        protected ViewModel(TModel model)
        {
            Model = model;
        }

        private TModel _Model;
        public TModel Model
        {
            get { return _Model; }
            protected set
            {
                _Model = value;
                ModelChanged();
            }
        }

        protected virtual void ModelChanged()
        {
            
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected bool TryGetValue<T>(Expression<Func<TViewModel, T>>  viewModelPropertySelector, out T value)
        {
            string viewModelProperty = viewModelPropertySelector.GetPropertyName();

            object currentValue;
            if (_Values.TryGetValue(viewModelProperty, out currentValue))
            {
                value = (T) currentValue;
                return true;
            }
            value = default(T);
            return false;
        }

        protected T GetChildViewModel<T>(Expression<Func<TViewModel, T>> viewModelPropertySelector)
            where T: IViewModel
        {
            string viewModelProperty = viewModelPropertySelector.GetPropertyName();

            IViewModel currentValue;
            if (_ChildViewModels.TryGetValue(viewModelProperty, out currentValue))
            {
                return (T) currentValue;
            }
            return default(T);
        }

        protected T GetValue<T>(Expression<Func<TViewModel, T>>  viewModelPropertySelector)
        {
            T result;
            return TryGetValue(viewModelPropertySelector, out result) ? result : default(T);
        }

        protected void SetValue<T>(Expression<Func<TViewModel, T>>  viewModelPropertySelector, T value)
        {
            string viewModelProperty = viewModelPropertySelector.GetPropertyName();

            object currentValue;
            if (_Values.TryGetValue(viewModelProperty, out currentValue))
            {
                if (EqualityComparer<T>.Default.Equals(value, (T)currentValue)) return;
            }

            _Values[viewModelProperty] = value;

            OnPropertyChanged(viewModelProperty);
        }

        protected void RegisterChildViewModel<T>(Expression<Func<TViewModel, T>> viewModelPropertySelector, T value)
            where T: IViewModel
        {
            string viewModelProperty = viewModelPropertySelector.GetPropertyName();

            _ChildViewModels[viewModelProperty] = value;

            OnPropertyChanged(viewModelProperty);
        }

       
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged.Raise(this, propertyName);
        }

        protected void OnPropertyChanged<T>(Expression<Func<TViewModel, T>> viewModelPropertySelector)
        {
            PropertyChanged.Raise(this, viewModelPropertySelector.GetPropertyName());
        }

        protected void AddFlagsEnumViewModelMapping<T, K>(Expression<Func<TViewModel, T>> viewModelPropertySelector, Expression<Func<TModel, K>> modelPropertySelector, K mask)
        {
            _Mappings[viewModelPropertySelector.GetPropertyName()] = new EnumMapping<TModel, K>(modelPropertySelector.GetPropertyName(), mask);
        }

        protected void AddSimpleMapping<T>(Expression<Func<TViewModel, T>> viewModelPropertySelector, Expression<Func<TModel, T>> modelPropertySelector, Func<string> validationMethod)
        {
            var propertyName = viewModelPropertySelector.GetPropertyName();
            _Mappings[propertyName] = new SimpleMapping<TModel>(modelPropertySelector.GetPropertyName());
            _ValidationRules[propertyName] = validationMethod;
        }

        protected void AddValidationRule<T>(Expression<Func<TViewModel, T>> viewModelPropertySelector, Func<string> validationMethod)
        {
            var propertyName = viewModelPropertySelector.GetPropertyName();
            _ValidationRules[propertyName] = validationMethod; 
        }
        protected void AddSimpleMapping<T>(Expression<Func<TViewModel, T>> viewModelPropertySelector, Func<string> validationMethod)
        {
            var propertyName = viewModelPropertySelector.GetPropertyName();
            _Mappings[propertyName] = new SimpleMapping<TModel>(propertyName);
            _ValidationRules[propertyName] = validationMethod;
        }

        public virtual void LoadValuesFromModel()
        {
            foreach (var mapping in _Mappings)
            {
                _Values[mapping.Key] = mapping.Value.LoadValue(Model);
                OnPropertyChanged(mapping.Key);
            }
            foreach (var value in _ChildViewModels)
            {
                value.Value.LoadValuesFromModel();
                OnPropertyChanged(value.Key);
            }
        }

        public virtual void SaveValuesToModel()
        {
            IEnumerable<string> validationSummary = GetValidationSummary();
            if (validationSummary.Any())
            {
                throw new ViewModelValidationFailedException("The ViewModel is not valid.", validationSummary);
            }

            foreach (var mapping in _Mappings)
            {
                object value;
                if (_Values.TryGetValue(mapping.Key, out value))
                {
                    mapping.Value.SetValue(value, Model);
                }
            }

            foreach (var childViewModel in _ChildViewModels.Values)
            {
                childViewModel.SaveValuesToModel();
            }
        }

        public IEnumerable<string> GetValidationSummary()
        {
            var errors = (from rule in _ValidationRules
                    let result = rule.Value()
                    where !string.IsNullOrEmpty(result)
                    select result).ToArray();
            var subErrors = from viewModel in _ChildViewModels.Values
                            from error in viewModel.GetValidationSummary()
                            select error;
            return errors.Union(subErrors);
        }
    }
}
